<?php
/***********
*  作者：bobo.j
*  功能：下载远程文件类
***********/
class DownLoader
{
	var $Url; //URL地址
	var $PartOfUrl; //URL里的部分
	var $RequestHeader ; //请求的头部信息
	var $PostBody ; //请求的头部信息
	var $ResponseHeader; //返回的头部信息
	var $Socket; //socket连接的指针
	var $ResponseBody; //返回的内容信息
	var $SavedLength;//已经下载的文件长度
	var $RefreshTime=10;  //每次读取所用的时间
	var $FileSavedDir='./soft';//已下载的文件存放目录

	function GetFile($Url = null,$act='down.php')
	{
		$this->start_time=time();
		$this -> Url = $Url?$Url:$this -> Url;
		$this -> cms_parseUrl($Url);
		$this -> OpenSocket();
		$this -> MakeDir($this->FileSavedDir,0777);
		$this -> SetSavedFileName();
		$this -> GetSavedFileLength();
		$this -> SetHeader();
		$this -> SentHeader();
		$this -> GetResponse();
		$this -> GetUsefullInfo();

		if($this->UsefullInfo['status_code']==206)
		{
			$this -> WriteToFile($this->SavedFileName,$this->ResponseBody);
			
			if($this -> Redirect($act,0))
			{
				return $this->SavedFileName;
			}
		}



		/*		if($this->UsefullInfo['status_code']==302)
		{
		echo $this->ReasonPhrase((int)$this->UsefullInfo['status_code']);
		$this->GetLocation();
		$this->Refresh($act,0);
		}
		else
		{
		$this -> WriteToFile($this->SavedFileName,$this->ResponseBody);
		$this -> Redirect($act,0,$endurl);
		}*/
	}

	/**
     * 函数
     * 功能 打开一个SOCKET连接
     * 参数
     * 返回 无
     */

	function OpenSocket()
	{
		$this -> Socket = @fsockopen($this -> PartOfUrl['host'], $this -> PartOfUrl['port']);
		if(!$this -> Socket)
		{
			die("远程文件无法打开");
		}
	}
	/**
     * 函数
     * 功能 发送头部信息
     * 参数
     * 返回
     */

	function SentHeader()
	{
		fwrite($this -> Socket, $this -> RequestHeader);
	}
	/**
     * 函数
     * 功能 取得服务器回应的内容包括头部，和主体两部分
     * 参数
     * 返回
     */

	function GetResponse($every_lenth = 8192)
	{
		do
		{
			$contents = fread($this -> Socket, $every_lenth);
			$response .= $contents;
			if ((time() - $this->start_time) > $this->RefreshTime)break;
		}
		while(strlen($contents) != 0);
		preg_match("/(.*?)\r\n\r\n(.*)/s", $response, $out);
		$this -> ResponseHeader = $out[1];
		$this -> ResponseBody = $out[2];
		fclose($this -> Socket);
	}
	/**
     * 函数
     * 功能 设置SOCKET连接是头部发送的信息
     * 参数
     * 返回
     */

	function SetHeader($method = null)
	{
		$header = "GET {$this -> Url} HTTP/1.1\r\n";
		$header .= "Host: {$this -> PartOfUrl['host']}\r\n";
		$header .= "User-Agent: WobabaDownloader/1.1\r\n";
		$header .= "Referer: {$this->PartOfUrl['dir']}\r\n";
		$header .= "Pragma: no-cache\r\n";
		$header .= "Cache-Control: no-cache\r\n";
		$header .= "Connection: close\r\n";
		$header .= "Range: bytes={$this->SavedFileSize}-\r\n\r\n";
		$this -> RequestHeader = $header;
	}

	/**
     * 函数
     * 功能 分割URL地址
     * 参数
     * 返回
     */

	function cms_parseUrl($Url)
	{
		$this -> Url = trim($Url);
		$PartOfUrl = parse_url($this -> Url);
		$PartOfUrl['path'] = isset($PartOfUrl['path'])?$PartOfUrl['path']:"/";

		$PartOfUrl['dir'] = $PartOfUrl['scheme'] . "://" . $PartOfUrl['host'] . (isset($PartOfUrl['port'])?':':null) . $PartOfUrl['port'] . substr($PartOfUrl['path'], 0, strrpos($PartOfUrl['path'], "/")) . '/';

		$PartOfUrl['prev_dir'] = $PartOfUrl['prev_dir'] == 'http://'?$PartOfUrl['dir']:$PartOfUrl['prev_dir'];

		$PartOfUrl['port'] = isset($PartOfUrl['port'])?$PartOfUrl['port']:80;
		$this -> PartOfUrl = $PartOfUrl;
		return $PartOfUrl;
	}
	/**
     * 函数
     * 功能 输出结果
     * 参数
     * 返回
     */

	function OutPut()
	{
		return $this -> ResponseBody;
	}
	/**
     * 函数
     * 功能 设置保存的文件名
     * 参数
     * 返回	URL地址MD5加密后的前四个字符-文件名
     */
	function SetSavedFileName()
	{
		$name=end(explode('/',$this->Url));
		
		$this->md5 =true;
		
		if($this->md5){
			$fix = '-' . md5($this -> Url);
			$fix_position=strrpos($name,'.');
			$this->SavedFileName=$this->FileSavedDir.'/'.substr_replace($name,$fix,$fix_position,0);
			//$this->SavedFileName=$this->FileSavedDir.'/'.$fix;
		}else{
			$this->SavedFileName=$this->FileSavedDir.'/'.$name;
		}
		return $this->SavedFileName;
	}

	/**
     * 函数
     * 功能 取得已经下载的文件长度
     * 参数
     * 返回
     */
	function GetSavedFileLength()
	{
		if(file_exists($this->SavedFileName))
		{
			$filesize=@filesize($this->SavedFileName);
		}
		else
		{
			$filesize=0;
		}
		$this->SavedFileSize=$filesize;
	}
	/**
     * 函数
     * 功能 调试
     * 参数
     * 返回
     */

	function Debug($object)
	{
		foreach (get_object_vars($object) as $vars => $values)
		{
			if(is_array($values))
			{
				$values=print_r($values,true);
			}
			if($vars == 'Socket')continue;
			if($vars == 'PostBody')continue;
			if($vars == 'ResponseBody')$values = nl2br(htmlspecialchars($values));
			$debug .= "<B>$vars</B>:\r\n$values\r\n\r\n";
		}
		return "<pre>$debug</pre>";
	}
	/**
     * 函数
     * 功能 跳转
     * 参数 $gotourl转入的URL地址，$delay延迟时间
     * 返回
     */
	function Redirect($gotourl,$delay)
	{
		if (($this->strlength+$this->SavedFileSize)==$this->UsefullInfo['remote_file_size'])
		{
			//echo ('已经完成读取');
			//$this->Refresh($endurl,$delay);
			return $this->SavedFileName;
		}
		else
		{
			echo $this->ReasonPhrase((int)$this->UsefullInfo['status_code']);
			//if($this->UsefullInfo['status_code']==416)exit;
			$this->ShowProcess();
			$this->Refresh($gotourl,$delay);exit;
		}
	}
	function refresh($gotourl,$delay)
	{
		echo "<meta http-equiv=\"refresh\" content=\"{$delay}; url={$gotourl}\" />";
	}
	/**
     * 函数
     * 功能 显示当前已经下载的百分比
     * 参数
     * 返回
     */
	function ShowProcess()
	{
		$current_get = $this->strlength+$this->SavedFileSize;
		$remote_file_size=(int)$this->UsefullInfo['remote_file_size'];
		echo @sprintf('已经读取%s%s', round($current_get/$remote_file_size * 100, 2), '%');
	}
	/**
     * 函数
     * 功能 写入文件
     * 参数 $filename文件名  $contents文件内容  $mode写入模式
     * 返回 写入数据的长度
     */
	function WriteToFile($filename, $contents, $mode = "ab")
	{
		@$fp = fopen($filename, $mode);
		if (!$fp)die("无法写入文件，请检查{$filename}是否存在");
		fwrite($fp, $contents);
		fclose($fp);
		return $this->strlength=strlen($contents);
	}
	/**
     * 函数
     * 功能 取得头不返回的有用信息
     * 参数
     * 返回 Array
(
    [remote_file_length] => 远程文件的大小
    [range_start] => 文件开始位置
    [reason_phrase] => 原因短语
    [status_code] => 状态码
)
     */
	function GetUsefullInfo()
	{
		preg_match("#[^\s]+\s(\d+)\s([^\r\n]+)#is",$this -> ResponseHeader,$info);
		list(,$usefull_info['status_code'],$usefull_info['reason_phrase'])=$info;
		if($usefull_info['status_code']==302)
		{
			$this->GetLocation();
		}
		else
		{
			preg_match("#Content-Range: bytes (\d*)-\d*/(\d*)#i",$this -> ResponseHeader,$info2);
			list(,$usefull_info['range_start'],$usefull_info['remote_file_size'])=$info2;
		}
		//print_r($info);
		//list(,$usefull_info['status_code'],$usefull_info['reason_phrase'],$usefull_info['range_start'],$usefull_info['remote_file_size'])=$info;
		$this->UsefullInfo=$usefull_info;
	}
	function GetLocation()
	{
		preg_match("#Location: ([^\r\n]+)#i",$this->ResponseHeader,$match);
		$this -> Url=$match[1];
	}
	function ReasonPhrase($status_code)
	{
		$reason_phrase=array(
		'206'=>'连接成功，可以下载！<BR>',
		'302'=>'您需要的资源需要重定向才能下载，正在为您重定向！<BR>',
		'403'=>'禁止访问！<BR>',
		'416'=>'服务器不能满足请求中指定的范围,有可能你已经下载过这个文件！<BR>'
		);
		return $reason_phrase[$status_code];
	}
	function MakeDir($pathname,$mode)
	{
		if(!is_dir($pathname))
		{
			$path=explode('/',$pathname);
			foreach($path as $dir)
			{
				$dirs.=$dir."/";
				@mkdir($dirs,$mode);
				@chmod($dirs,$mode);
			}
		}
		return true;
	}

	function makename($length)
	{
		$possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
		$str = "";
		while ( strlen( $str ) < $length )
		{
			$str .= substr( $possible, rand( ) % strlen( $possible ), 1 );
		}
		return $str;
	}


}
?>